///////////////////////////////////////////////////////////////////////////////////////////////////
/// \file formic/wfn/configuration.h
///
/// \brief   header file for the Configuration and ConfigDiff classes
///
///////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef FORMIC_WFN_CONFIGURATION_HEADER
#define FORMIC_WFN_CONFIGURATION_HEADER

#include<vector>
#include<cassert>
#include<string>

namespace formic {

  class InputBase;
  class Configuration;

  ///////////////////////////////////////////////////////////////////////////////////////////////////
  /// \brief a class to hold information about the difference between two configurations
  ///
  ///////////////////////////////////////////////////////////////////////////////////////////////////
  class ConfigDiff {

    private:

      /// \brief  sites in the fock vector that are empty in new but occupied in old
      std::vector<int> _newly_empty;

      /// \brief  sites in the fock vector that are occupied in new but empty in old
      std::vector<int> _newly_full;

    public:

      std::vector<int> & newly_empty() { return _newly_empty; }
      std::vector<int> & newly_full() { return _newly_full; }

      const std::vector<int> & newly_empty() const { return _newly_empty; }
      const std::vector<int> & newly_full() const { return _newly_full; }

      ConfigDiff();

      ConfigDiff(const formic::InputBase & userinp);

      ConfigDiff(const formic::Configuration & new_config, const formic::Configuration & old_config);

  };

  ///////////////////////////////////////////////////////////////////////////////////////////////////
  /// \brief  a class to hold a configuration
  ///
  ///////////////////////////////////////////////////////////////////////////////////////////////////
  class Configuration {

    private:

      /// \brief  the number of sites
      int _ns;

      /// \brief  the total occupation of the lattice (for electrons, this is the total electron number)
      int _to;

      /// \brief  the number of configurations on each site (2 for fermions, i.e. occupied or empty)
      int _cps;

      /// \brief  flag to tell whether or not all configurations have been looped over
      bool _finished;

      /// \brief  the occupation number vector
      std::vector<int> _occ_vec;

      /// \brief  the number of times each occupation occurs
      std::vector<int> _occurances;

      /// \brief  the number of alpha sites
      int _nas;

      /// \brief  the number of beta sites
      int _nbs;

      /// \brief  the number of alpha particles
      int _nap;

      /// \brief  the number of beta particles
      int _nbp;

      /// \brief  the number of alpha holes
      int _nau;

      /// \brief  the number of beta holes
      int _nbu;

//      /// \brief  the configuration of the fock vector, in ababab... format
//      std::vector<int> _fock_config;

      /// \brief  for each alpha site, the position of that site (if occupied) in the list of occupied sites
      std::vector<int> _a_site_to_occ;

      /// \brief  for each beta site, the position of that site (if occupied) in the list of occupied sites
      std::vector<int> _b_site_to_occ;

      /// \brief  for each alpha site, the position of that site (if unoccupied) in the list of unoccupied sites
      std::vector<int> _a_site_to_unocc;

      /// \brief  for each beta site, the position of that site (if unoccupied) in the list of unoccupied sites
      std::vector<int> _b_site_to_unocc;

      /// \brief  the site indeces of the occupied alpha sites
      std::vector<int> _a_occ_to_site;

      /// \brief  the site indeces of the occupied beta sites
      std::vector<int> _b_occ_to_site;

      /// \brief  the site indeces of the unoccupied alpha sites
      std::vector<int> _a_unocc_to_site;

      /// \brief  the site indeces of the unoccupied beta sites
      std::vector<int> _b_unocc_to_site;

    public:

      int cps() const { return _cps; }

      std::vector<int> & fock_config() { return _occ_vec; }
      const std::vector<int> & fock_config() const { return _occ_vec; }

//      int & operator[](const int i) { assert( i >= 0 && i < _fock_config.size() ); return _fock_config[i]; }
//      int operator[](const int i) const { assert( i >= 0 && i < _fock_config.size() ); return _fock_config[i]; }

      int & operator[](const int i) { assert( i >= 0 && i < _occ_vec.size() ); return _occ_vec[i]; }
      int operator[](const int i) const { assert( i >= 0 && i < _occ_vec.size() ); return _occ_vec[i]; }

      const int * c_ptr() const { assert( _occ_vec.size() > 0 ); return &_occ_vec[0]; }
      int * c_ptr() { assert( _occ_vec.size() > 0 ); return &_occ_vec[0]; }

      int nap() const { return _nap; }
      int nbp() const { return _nbp; }
      int nau() const { return _nau; }
      int nbu() const { return _nbu; }

      const std::vector<int> & a_site_to_occ   () const { return _a_site_to_occ;   }
      const std::vector<int> & b_site_to_occ   () const { return _b_site_to_occ;   }
      const std::vector<int> & a_site_to_unocc () const { return _a_site_to_unocc; }
      const std::vector<int> & b_site_to_unocc () const { return _b_site_to_unocc; }
      const std::vector<int> & a_occ_to_site   () const { return _a_occ_to_site;   }
      const std::vector<int> & b_occ_to_site   () const { return _b_occ_to_site;   }
      const std::vector<int> & a_unocc_to_site () const { return _a_unocc_to_site; }
      const std::vector<int> & b_unocc_to_site () const { return _b_unocc_to_site; }

      Configuration(const int ns, const int to, const int cps);
      Configuration(const formic::InputBase & userinp);

      Configuration & operator=(const Configuration & other);

      int occ_unocc_cmpd(int occ_site, int unocc_site) const;

      template<class GEN> static double propose_move(const formic::InputBase & userinp,
                                                     GEN & gen,
                                                     const formic::Configuration & config,
                                                     formic::ConfigDiff & config_diff);

      void init_occ_unocc_site();
      void init_occ_unocc_site(const int b);

      void update(const formic::ConfigDiff & diff);

      void reset();
      void operator++();
      void operator++(int) { ++(*this); }
      bool finished() const { return _finished; }

      void populate_alpha_or_beta(const int b, int x);
      int get_irrep(const int nirr, const std::vector<int> & orb_irrep, const std::vector<int> & dir_prod_table) const;

//      /// \brief  counts the number of alpha or beta electrons in a configuration
//      static int count_pop(const int no, const int b, const formic::Configuration & config) {
//        int retval = 0;
//        for (int i = 0; i < no; i++) {
//          assert( config[2*i+b] == 0 || config[2*i+b] == 1 );
//          retval += config[2*i+b];
//        }
//        return retval;
//      }

  };

  void check_config_update(const formic::ConfigDiff & diff,
                           const formic::Configuration & config,
                           const std::string & calling_func);

}

#endif
